
/* Generated by Interface Builder */

#import "PCHPlot.h"
#import "LPCView.h"
#import "Dispatcher.h"
#import <appkit/Application.h>
#import <appkit/Window.h>
#import <appkit/ScrollView.h>
#import <appkit/Panel.h>
#import <appkit/Cell.h>
#import <appkit/SavePanel.h>
#import <appkit/OpenPanel.h>
#import <strings.h>
#import <sys/types.h>
#import <sys/stat.h>
#import <sys/file.h>
#import <libc.h>
#import <dpsclient/dpsclient.h>
#import <math.h>
#import <sound/sound.h>
#import <stdio.h>
#import <streams/streams.h>
#import <dpsclient/wraps.h>
#import "PWfft.h"

#define PITCH	0
#define AMP	1
#define SIZE	2

#define MAXZOOM	4
#define MINZOOM	-3

@implementation PCHPlot

extern int errno;

+ new:(const char *)title
{
	NXRect aRect;
	
	self = [super new];
	// Set up memory
	PSops = NULL;
	PSAdata = NULL;
	PSPdata = NULL;
	[NXApp loadNibSection:"pchplot.nib" owner:self];
	[myView setDelegate:self];
	[myView setParent:self];
	[myPlot setDelegate:self];
	numBytes = 0;	
	zoomfactor = 1;
	curzoom = 0;
	PWinit();
	[self setDirty:NO];

	// Set up view dependencies
	
	[myScroll getDocVisibleRect:&aRect];
	[myView setFrame:&aRect];
	[myScroll setDocView:myView];
	[myScroll setHorizScrollerRequired:YES];
	[myScroll setVertScrollerRequired:NO];
	
	// Set the title of the window - filename or Untitled 
	strcpy(filename, title);
	if (!strcmp(filename, "Untitled")) {
		hasData = NO;
		[myPlot setTitleAsFilename:"Untitled"];
		[self show:self];
		return self;
	}
	else {
		[myPlot setTitleAsFilename:filename];
		if ([self setPCHfile:filename] < 0) {
			hasData = NO;
			[myPlot setTitleAsFilename:"Untitled"];
			[self show:self];
			return self;
		}
		hasData = YES;
	}
	[self show:self];
	return self;
}

- (int) setPCHfile:(const char *)file
{
	struct stat st;
	
	openfd = open(file, O_RDWR, 0);
	if (openfd < 0) {
		[self error:"Could not open input file : ":errno];
		return -1;
	}
	fstat(openfd, &st);
	frames = (st.st_size/(SIZE*(sizeof(float))))-1;
	numBytes = st.st_size;
	[numFrames setIntValue:frames];
	pchData = (float *)malloc(st.st_size);
	if (read(openfd, pchData, st.st_size) < 0) {
		[self error:"Error reading input file : ":errno];
		return -1;
	}
	return 0;	
}

- drawData
{
	int 	i, winwidth;
	float 	yscalea, yscalep, half, zoomsize;
	NXRect 	viewBounds, visBounds, scrollBounds;
	NXSize	scrollSize;
	float	amax, amin;	// amplitude boundaries
	float	ptchmax, ptchmin;	// pitch boundaries 
	// DPSUserPath stuff
	float	bbox[4];	
	float	*t, *a, *ptch;	// temporary variables
	char	*o;
	
	//  Only draw plot if such information exists
	if (!hasData) 
		return self;
		
	amax = MINFLOAT;  ptchmax = MINFLOAT;
	amin = MAXFLOAT;  ptchmin = MAXFLOAT;
	
	t = pchData;
	for (i = 0; i < frames; i++) {
		if (t[AMP] > amax) 
			amax = t[AMP];
		if (t[AMP] < amin)
			amin = t[AMP];
		if (t[PITCH] > ptchmax)
			ptchmax = t[PITCH];
		if (t[PITCH] < ptchmin)
			ptchmin = t[PITCH];
		t += SIZE;
	}
	if (amax == amin) {
		if (amax > 0)
			amin = 0;
		else if (amax == 0)
			amin = -1;
		else 
			amin = 2*amax;
	}
	if (ptchmax == ptchmin) {
		if (ptchmax > 0)
			ptchmin = 0;
		else if (ptchmax == 0)
			ptchmin = -1;
		else
			ptchmin = 2*ptchmax;
	}

	[myView getBounds:&viewBounds];	
	[myView getVisibleRect:&visBounds];
	[myScroll getContentSize:&scrollSize];
	
	[myScroll getDocVisibleRect:&scrollBounds];
	[myView getVisibleRect:&visBounds];
	scrollBounds.size.width *= zoomfactor;
	/* Bug in DSPUserpath makes drawing at > 4096 bomb */
	while (scrollBounds.size.width > 4096.0) {
		// Simulate a zoom out
		scrollBounds.size.width /= zoomfactor;
		--curzoom;
		zoomfactor /= 1.5;
		scrollBounds.size.width *= zoomfactor;
		printf("too big -- new width = %f\n", scrollBounds.size.width);
	}
	[myView sizeTo:scrollBounds.size.width :scrollBounds.size.height];
	[myView getBounds:&viewBounds];
	[myView lockFocus];
	PSsetgray(NX_WHITE);
	NXRectFill(&viewBounds);
	[myView unlockFocus];
	winwidth = scrollBounds.size.width;
	zoomsize = frames;
	step = 1.0*zoomsize/winwidth;
	half = 1.0*(visBounds.size.height)/2.0;
	yscalea = 1.0*(half - 25.0)/(1.0*(amax - amin));
	yscalep = 1.0*(half - 25.0)/(1.0*(ptchmax - ptchmin));
	if (!PSAdata) PSAdata = (float *)malloc(4*winwidth*sizeof(float));
	if (!PSPdata) PSPdata = (float *)malloc(4*winwidth*sizeof(float));
	if (!PSops) PSops = (char *)malloc(2*winwidth*sizeof(char));
	
	t = pchData;
	for (i = 0, a = PSAdata, ptch = PSPdata, o = PSops; 
		i < winwidth; i++) {
		*a++ = i;
		*a++ = 20.0;
		*a++ = 0.0;
		*a++ = ((t[AMP] - amin)*yscalea);

		*ptch++ = i;	
		*ptch++ = half+20.0;
		*ptch++ = 0.0;
		*ptch++ = ((t[PITCH] - ptchmin)*yscalep);
		*o++ = dps_moveto;
		*o++ = dps_rlineto;

		t = (pchData) + (SIZE * (int)(i * step));
	}
	bbox[0] = 0.0;
	bbox[1] = 0.0;
	bbox[2] = winwidth;
	bbox[3] = ((amax - amin)*yscalea) + half;
	[myView drawPlot:PSAdata:PSops:bbox:winwidth:NX_BLACK];
	bbox[1] = half;
	bbox[3] = visBounds.size.height;
	[myView drawPlot:PSPdata:PSops:bbox:winwidth:NX_BLACK];
	PWdrawruler(0, frames,(20*frames/winwidth), (20*frames/winwidth)/step);
	if (PSAdata) free(PSAdata);
	if (PSPdata) free(PSPdata);
	if (PSops) free(PSops);
	PSAdata = NULL;
	PSPdata = NULL;
	PSops = NULL;
	return self;
}

- show:sender
{
	[myPlot makeKeyAndOrderFront:self];
	[myView display];	
	return self;
}

- zoomIn:sender
{
	NXRect foo;
	
	if (curzoom >= MAXZOOM) 
		return self;
	[myView getBounds:&foo];
	if ((foo.size.width * 1.5) > 4096.0)
		return self;
	++curzoom;
	zoomfactor *= 1.5;
	[myView display];	
	return self;
}

- zoomOut:sender
{
	if (curzoom <= MINZOOM)
		return self;
	--curzoom;
	zoomfactor /= 1.5;
	[myView display];
	return self;
}

- print:sender
{
	[myView printPSCode:sender];
	return self;	
}

- updateCursor:sender
{
	float	amp, pitch, *t;
	int	cursor, width, frame1, frame2, temp;
	
	if (!hasData) 
		return self;
	cursor = [myView getcurPos];
	width = [myView getWidth];

	frame1 = (int)(cursor * step);
	frame2 = (int)((cursor + width) * step);
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	[startFrame setIntValue:frame1];
	[endFrame setIntValue:frame2];
	t = pchData + (SIZE * (int)((cursor + width) * step));
	amp = t[AMP];
	pitch = t[PITCH];
	[ampFrame setFloatValue:amp];
	[pitchFrame setFloatValue:pitch];
	[playFrame1 setIntValue:(frame1 + 1)];
	[playFrame2 setIntValue:(frame2 + 1)];
	return self;
}

- selectAll:sender
{
	[myView changeCurs:0:(frames/step)];
	[playFrame1 setIntValue:1];
	[playFrame2 setIntValue:(frames+1)];
	return self;
}

- cursorSel:sender
{
	[myView setCursorType:SELECTION];
	return self;
}

- cursorHair:sender
{
	[myView setCursorType:HAIRLINE];
	return self;
}

- changeCursor:sender
{
	int	frame1, frame2, temp;
	float	loc, loc2, width;
	
	frame1 = [startFrame intValue];
	frame2 = [endFrame intValue];

	frame1 = (frame1 < 0) ? 0 : frame1;
	frame1 = (frame1 > frames) ? frames : frame1;
	frame2 = (frame2 < 0) ? 0 : frame2;
	frame2 = (frame2 > frames) ? frames : frame2;

	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
		
	loc = frame1 / step;
	loc2 = frame2 / step;
	width = loc2 - loc;
	[myView changeCurs:loc:width];
	return self;
}

- changeAmp:sender
{
	float	*t, val;
	int	cursor, width, i, frame1, frame2, temp;
	
	cursor = [myView getcurPos];
	width = [myView getWidth];
	frame1 = (int)cursor * step;
	frame2 = (int)(cursor + width) * step;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	t = pchData + (SIZE * frame1);
	val = [ampFrame floatValue];
	[self saveundo:(frame2 - frame1 + 1)*SIZE*sizeof(float):t];
	for (i = frame1; i <= frame2; i++) {
		t[AMP] = val;
		t = (pchData) + (SIZE * (i + 1));
	}
	[myView display];
	[self setDirty:YES];
	return self;		
}

- changePitch:sender
{
	float	*t, val;
	int	cursor, width, i, frame1, frame2, temp;
	
	cursor = [myView getcurPos];
	width = [myView getWidth];
	frame1 = (int)cursor * step;
	frame2 = (int)(cursor + width) * step;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	t = pchData + (SIZE * frame1);
	val = [pitchFrame floatValue];
	[self saveundo:(frame2 - frame1 + 1)*SIZE*sizeof(float):t];
	for (i = frame1; i <= frame2; i++) {
		t[PITCH] = val;
		t = (pchData) + (SIZE * (i + 1));
	}
	[myView display];
	[self setDirty:YES];
	return self;
}

- doCopy:(NXStream *)selection
{
	float	*t;
	int	cursor, width, frame1, frame2, temp, bytes;
	
	if (!hasData) {
		return self;
	}
	cursor = [myView getcurPos];
	width = [myView getWidth];
	frame1 = (int)cursor * step;
	frame2 = (int)(cursor + width) * step;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	t = pchData + (SIZE * frame1);
	bytes =  (frame2 - frame1)*SIZE*sizeof(float);
	NXWrite(selection, t, bytes);
	return self;
	
}

- doCut:(NXStream *)selection
{
	float	*t, *v;
	int	cursor, width, i, frame1, frame2, temp, bytes;

	if (!hasData) {
		return self;
	}
	cursor = [myView getcurPos];
	width = [myView getWidth];
	frame1 = (int)cursor * step;
	frame2 = (int)(cursor + width) * step;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	t = pchData + (SIZE * frame1);
	bytes =  (frame2 - frame1)*SIZE*sizeof(float);
	NXWrite(selection, t, bytes);
	numBytes -= bytes;
	frames = (numBytes/(SIZE*sizeof(float)))-1;
	[numFrames setIntValue:frames];
	v = t + (bytes / sizeof(float));
	for (i = 0; i < (bytes / sizeof(float)); i++) {
		*t++ = *v++;
	}
	realloc(pchData, numBytes);
	[myView display];
	[self setDirty:YES];
	return self;
	
}

- doInsert:(float *)data :(int) bytes
{
	int	cursor, frame1, size, i;
	float	*t, *v;
	
	if (!hasData) {
		hasData = 1;
		pchData = (float *)malloc(bytes);
		numBytes = bytes;
		frames = (numBytes/(SIZE*sizeof(float))) - 1;
		[numFrames setIntValue:frames];
		t = pchData;
		for (i = 0; i < (bytes / sizeof(float)); i++) {
			*t++ = *data++;
		}
		[myView display];
		return self;
	}
	numBytes += bytes;
	frames = (numBytes/(SIZE*sizeof(float)))-1;
	[numFrames setIntValue:frames];
	realloc(pchData, numBytes);
	cursor = [myView getcurPos];
	frame1 = (int)cursor * step;
	size = (numBytes - bytes) - (frame1 * SIZE * sizeof(float));

	v = pchData + (numBytes / sizeof(float)) - 1;
	t = pchData + ((numBytes - bytes) / sizeof(float)) - 1;
	for (i = 0; i < (size / sizeof(float)); i++) {
		*v-- = *t--;
	}
	t = pchData + (SIZE * frame1);
	for (i = 0; i < (bytes / sizeof(float)); i++) {
		*t++ = *data++;
	}
	[myView display];
	[self setDirty:YES];
	return self;
}

- saveundo:(int)size: (float *)data
{
	int	i;
	float	*t, *s;
	
	if (undoData) 
		free(undoData);
	undoData = (float *)malloc(size);
	undoLoc = data;
	undoSize = size;
	t = undoData;
	s = data;
	for (i = 0; i < (undoSize / 4); i++) {
		*t++ = *s++;
	}
	return self;
}

- undo:sender
{
	float	*t, *s;
	int	i;
	
	t = undoLoc;
	s = undoData;
	for (i = 0; i < (undoSize / 4); i++) {
		*t++ = *s++;
	}
	[myView display];
	return self;
}

- multiplyAmp:sender
{
	float	*t, val;
	int	cursor, width, i, frame1, frame2, temp;
	
	cursor = [myView getcurPos];
	width = [myView getWidth];
	frame1 = (int)cursor * step;
	frame2 = (int)(cursor + width) * step;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	val = [mulAmpFrame floatValue];
	t = pchData + (SIZE * frame1);
	[self saveundo:(frame2 - frame1 + 1)*SIZE*sizeof(float):t];
	for (i = frame1; i <= frame2; i++) {
		t[AMP] *= val;
		t = (pchData) + (SIZE * (i + 1));
	}
	[myView display];
	[self setDirty:YES];
	return self;
}

- multiplyPitch:sender
{
	float	*t, val, top, bottom;
	int	cursor, width, i, frame1, frame2, temp;
	
	cursor = [myView getcurPos];
	width = [myView getWidth];
	t = pchData + (SIZE * (int)(cursor * step));
	frame1 = (int)cursor * step;
	frame2 = (int)(cursor + width) * step;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	val = [mulPitchFrame floatValue];
	top = [topPitchFrame floatValue];
	bottom = [botPitchFrame floatValue];
	t = pchData + (SIZE * frame1);
	[self saveundo:(frame2 - frame1 + 1)*SIZE*sizeof(float):t];	
	for (i = frame1; i <= frame2; i++) {
		if (t[PITCH] <= top && t[PITCH] >= bottom)
			t[PITCH] *= val;
		t = (pchData) + (SIZE * (i + 1));
	}
	[myView display];
	[self setDirty:YES];
	return self;
}

- interpolate:sender
{
	float	*t, *v, first, last, scale;
	int	cursor, width, i, frame1, frame2, temp, j;
	
	cursor = [myView getcurPos];
	width = [myView getWidth];
	frame1 = ((int)(cursor * step)) - 1;
	frame2 = ((int)((cursor + width) * step)) + 1;
	if (frame1 > frame2) {
		temp = frame1;
		frame1 = frame2;
		frame2 = temp;
	}
	t = pchData + (SIZE * frame1);
	v = pchData + (SIZE * frame2);
	first = t[PITCH];
	last = v[PITCH]; 
	t = pchData + (SIZE * frame1);
	scale = (last - first) / (frame2 - frame1);
	[self saveundo:(frame2 - frame1 + 1)*SIZE*sizeof(float):t];	
	for (i = frame1, j = 0; i <= frame2; i++) {
		t[PITCH] = first + (j * scale);
		t = (pchData) + (SIZE * (i + 1));
		++j;
	}
	[myView display];
	[self setDirty:YES];
	return self;
}

- save:sender
{
	id	sPanel;
	
	sPanel = [SavePanel new];
	if (!strcmp("Untitled", filename)) {
		if ([sPanel runModal]) {
			strcpy(filename, [sPanel filename]);
			[myPlot setTitleAsFilename:filename];
			openfd = open(filename, O_RDWR|O_CREAT, 0644);
			if (openfd < 0) {
				[self error:"Error opening input file : ":errno];
				return self;
			}
			if (write(openfd, (char *)pchData, numBytes) < 0) {
				[self error:"Error writing file : ":errno];
				return self;
			};
			[self setDirty:NO];
			return self;
		}
	} 
	else {
		lseek(openfd, 0, 0);  // start of file
		if (write(openfd,(char *)pchData,numBytes) < 0) {
			[self error:"Error writing file : ":errno];
			return self;
		}
		[self setDirty:NO];
		return self;
	}
	return self;
}

- saveAs:sender
{
	id	sPanel;
	char	dir[1024],  *file;
	
	sPanel = [SavePanel new];
	file = rindex(filename, '/') + 1;
	strncpy(dir, filename, (int)(file - filename));
	if ([sPanel runModalForDirectory:dir file:file]) {
		if (strcmp(filename, [sPanel filename])) {
			strcpy(filename, [sPanel filename]);
			close(openfd);
			openfd = open(filename, O_RDWR|O_CREAT, 0644);
			if (openfd < 0) {
				[self error:"Error saving file : ":errno];
				return self;
			}
			if (write(openfd, (char *)pchData, numBytes) < 0) {
				[self error:"Error saving file : ":errno];
				return self;
			}
			[myPlot setTitleAsFilename:filename];
			[self setDirty:NO];
			return self;
		}
		else {
			lseek(openfd, 0, 0);
			if (write(openfd, (char *)pchData, numBytes) < 0) {
				[self error:"Error saving file : ":errno];
				return self;
			}
			[self setDirty:NO];
			return self;
		}
	}
	return self;
}

- setOutputFile:sender
{
	id	openP;
	char	*types[2];
	
	types[0] = "snd";
	types[1] = 0;
	openP = [OpenPanel new];
	[openP runModalForTypes:types];
	if ([openP filenames]) {
		strcpy(soundFilename, [openP filename]);
		[soundFileFrame setStringValue:soundFilename];
	}	
	return self;
}

- playOutputSound:sender
{
 	int	err;
	SNDSoundStruct *s;
	
	strcpy(soundFilename, [soundFileFrame stringValue]);
	if (!strlen(soundFilename)) 
		return self;
	err = SNDReadSoundfile(soundFilename, &s);
	if (!err) {
		err = SNDStartPlaying(s, 1, 5, 0, 0,(SNDNotificationFun)SNDFree);
		if (!err)
			SNDWait(1);
		else 
			[self error:"Error playing soundfile":0];
	}
	else {
		[self error:"Error reading soundfile":0];
	}
	return self;
}

- play:sender
{
    return self;
}

- doPitchAnal:sender
{
	char	cmd[1024];
	
	strcpy(soundFilename, [soundFileFrame stringValue]);
	if (!strlen(soundFilename)) {
		strcpy(soundFilename, "/tmp/anal.snd");
		[soundFileFrame setStringValue:soundFilename];
	}
	sprintf(cmd, "%s %s %s %d %d", [NXApp playpitch], filename, soundFilename, [playFrame1 intValue], [playFrame2 intValue]);
	system(cmd);
	return self;
}

- drawPlot:sender
{
    return self;
}

- plot
{
	return myPlot;
}

- error:(char *)msg:(int)num
{
	if (num)
		NXRunAlertPanel("Alert", "%s : %s", "OK", NULL, NULL, msg, strerror(num));
	else
		NXRunAlertPanel("Alert", "%s", "OK", NULL, NULL, msg);
	return self;
}

- windowWillClose:sender
{
	if ([myPlot isDocEdited]) {
		switch (NXRunAlertPanel("Alert", "Plot has been edited.  Do you want to save?", "Yes", "No", "Cancel")) {
			case NX_ALERTDEFAULT :
				[self saveAs:self];
				break;
			case NX_ALERTOTHER :
				return nil;
				break;
			default :
				break;
		}
	}
	return self;
}

- setDirty:(BOOL)val
{
	[myPlot setDocEdited:val];
	dirty = val;
	return self;
}

- (BOOL)isDirty
{
	return dirty;
}

@end
